<template>
  <div class="png-to-ico-converter">
    <h2>PNG 转 ICO 转换器</h2>

    <!-- 文件上传区域 -->
    <div class="upload-area" @click="triggerFileSelect" @dragover.prevent @drop.prevent="handleDrop">
      <div v-if="!selectedFile" class="upload-placeholder">
        <i class="el-icon-upload"></i>
        <p>点击选择 PNG 图片或拖拽到此处</p>
        <p class="tip">支持 PNG 格式</p>
      </div>

      <div v-else class="file-info">
        <img :src="previewUrl" alt="预览" class="preview-image" />
        <p>{{ selectedFile.name }}</p>
        <p>{{ formatFileSize(selectedFile.size) }}</p>
        <el-button @click.stop="removeFile" type="danger" size="small">移除</el-button>
      </div>
    </div>

    <input ref="fileInput" type="file" accept=".png" @change="handleFileSelect" style="display: none" />

    <!-- 转换选项 -->
    <div v-if="selectedFile" class="convert-options">
      <h3>转换选项</h3>

      <div class="option-item">
        <label>ICO 尺寸:</label>
        <el-select v-model="selectedSizes" multiple placeholder="选择 ICO 尺寸">
          <el-option v-for="size in availableSizes" :key="size" :label="`${size}x${size}`" :value="size" />
        </el-select>
        <span class="tip">可选择多个尺寸，生成多尺寸 ICO 文件</span>
      </div>

      <div class="option-item">
        <label>输出文件名:</label>
        <el-input v-model="outputFileName" placeholder="输出文件名" />
      </div>

      <div class="option-item">
        <label>保持透明度:</label>
        <el-switch v-model="preserveTransparency" />
      </div>
    </div>

    <!-- 转换按钮 -->
    <div v-if="selectedFile" class="convert-section">
      <el-button type="primary" size="large" @click="convertImage" :loading="converting"
        :disabled="!selectedFile || selectedSizes.length === 0">
        {{ converting ? '转换中...' : '开始转换' }}
      </el-button>
    </div>

    <!-- 转换结果 -->
    <div v-if="convertedImage" class="result-section">
      <h3>转换结果</h3>
      <div class="result-preview">
        <div class="ico-previews">
          <div v-for="size in selectedSizes" :key="size" class="ico-preview-item">
            <img :src="getPreviewForSize(size)" :alt="`${size}x${size}`" class="ico-preview" />
            <span>{{ size }}x{{ size }}</span>
          </div>
        </div>
        <div class="result-info">
          <p>文件名: {{ convertedImage.fileName }}</p>
          <p>文件大小: {{ formatFileSize(convertedImage.size) }}</p>
          <p>格式: ICO</p>
          <p>包含尺寸: {{ selectedSizes.join('x, ') }}x</p>
        </div>
      </div>

      <div class="download-section">
        <el-button type="success" @click="downloadImage">下载 ICO 文件</el-button>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'
import { ElMessage } from 'element-plus'

// 响应式数据
const selectedFile = ref<File | null>(null)
const previewUrl = ref<string>('')
const converting = ref<boolean>(false)
const outputFileName = ref<string>('')
const selectedSizes = ref<number[]>([16, 32, 48])
const preserveTransparency = ref<boolean>(true)
const convertedImage = ref<{
  dataUrl: string
  fileName: string
  size: number
  blob: Blob
} | null>(null)
const previewCache = ref<Map<number, string>>(new Map())

// DOM 引用
const fileInput = ref<HTMLInputElement>()

// 可用的 ICO 尺寸
const availableSizes = [16, 24, 32, 48, 64, 96, 128, 256]

// 计算属性
const outputFileNameComputed = computed(() => {
  if (outputFileName.value) {
    return outputFileName.value.replace(/\.png$/i, '') + '.ico'
  }
  if (selectedFile.value) {
    return selectedFile.value.name.replace(/\.png$/i, '.ico')
  }
  return 'converted.ico'
})

// 触发文件选择
const triggerFileSelect = () => {
  fileInput.value?.click()
}

// 处理文件选择
const handleFileSelect = (event: Event) => {
  const target = event.target as HTMLInputElement
  const file = target.files?.[0]
  if (file) {
    handleFile(file)
  }
}

// 处理拖拽文件
const handleDrop = (event: DragEvent) => {
  const files = event.dataTransfer?.files
  if (files && files.length > 0) {
    handleFile(files[0])
  }
}

// 处理文件
const handleFile = (file: File) => {
  // 验证文件类型
  if (!file.type.match(/^image\/png$/)) {
    ElMessage.error('请选择 PNG 格式的图片')
    return
  }

  // 验证文件大小 (限制为 10MB)
  if (file.size > 10 * 1024 * 1024) {
    ElMessage.error('文件大小不能超过 10MB')
    return
  }

  selectedFile.value = file

  // 生成预览 URL
  const reader = new FileReader()
  reader.onload = (e) => {
    previewUrl.value = e.target?.result as string
  }
  reader.readAsDataURL(file)

  // 设置默认输出文件名
  if (!outputFileName.value) {
    outputFileName.value = file.name.replace(/\.png$/i, '')
  }

  // 清除之前的转换结果
  convertedImage.value = null
  previewCache.value.clear()
}

// 移除文件
const removeFile = () => {
  selectedFile.value = null
  previewUrl.value = ''
  outputFileName.value = ''
  convertedImage.value = null
  previewCache.value.clear()
  if (fileInput.value) {
    fileInput.value.value = ''
  }
}

// 创建指定尺寸的 Canvas
const createCanvasForSize = async (img: HTMLImageElement, size: number): Promise<HTMLCanvasElement> => {
  const canvas = document.createElement('canvas')
  const ctx = canvas.getContext('2d')

  if (!ctx) {
    throw new Error('无法获取 Canvas 上下文')
  }

  canvas.width = size
  canvas.height = size

  // 如果不保持透明度，先填充白色背景
  if (!preserveTransparency.value) {
    ctx.fillStyle = '#FFFFFF'
    ctx.fillRect(0, 0, size, size)
  }

  // 计算缩放比例，保持图片比例
  const scale = Math.min(size / img.width, size / img.height)
  const scaledWidth = img.width * scale
  const scaledHeight = img.height * scale

  // 居中绘制
  const x = (size - scaledWidth) / 2
  const y = (size - scaledHeight) / 2

  ctx.drawImage(img, x, y, scaledWidth, scaledHeight)

  return canvas
}

// 将数字转换为小端序字节数组
const toLittleEndian = (num: number, bytes: number): Uint8Array => {
  const result = new Uint8Array(bytes)
  for (let i = 0; i < bytes; i++) {
    result[i] = (num >> (i * 8)) & 0xFF
  }
  return result
}

// 创建 ICO 文件
const createIcoFile = async (canvases: HTMLCanvasElement[]): Promise<Blob> => {
  const images: Uint8Array[] = []
  let totalSize = 6 + (canvases.length * 16) // ICO 头部 + 目录条目

  // 为每个 Canvas 生成 PNG 数据
  for (const canvas of canvases) {
    const pngDataUrl = canvas.toDataURL('image/png')
    const pngData = atob(pngDataUrl.split(',')[1])
    const pngBytes = new Uint8Array(pngData.length)
    for (let i = 0; i < pngData.length; i++) {
      pngBytes[i] = pngData.charCodeAt(i)
    }
    images.push(pngBytes)
    totalSize += pngBytes.length
  }

  const icoData = new Uint8Array(totalSize)
  let offset = 0

  // ICO 文件头
  icoData.set([0, 0], offset) // Reserved
  offset += 2
  icoData.set([1, 0], offset) // Type (1 = ICO)
  offset += 2
  icoData.set(toLittleEndian(canvases.length, 2), offset) // Number of images
  offset += 2

  // 目录条目
  let imageOffset = 6 + (canvases.length * 16)
  for (let i = 0; i < canvases.length; i++) {
    const canvas = canvases[i]
    const imageData = images[i]

    icoData[offset] = canvas.width === 256 ? 0 : canvas.width // Width
    icoData[offset + 1] = canvas.height === 256 ? 0 : canvas.height // Height
    icoData[offset + 2] = 0 // Color palette
    icoData[offset + 3] = 0 // Reserved
    icoData.set(toLittleEndian(1, 2), offset + 4) // Color planes
    icoData.set(toLittleEndian(32, 2), offset + 6) // Bits per pixel
    icoData.set(toLittleEndian(imageData.length, 4), offset + 8) // Image size
    icoData.set(toLittleEndian(imageOffset, 4), offset + 12) // Image offset

    offset += 16
    imageOffset += imageData.length
  }

  // 图像数据
  for (const imageData of images) {
    icoData.set(imageData, offset)
    offset += imageData.length
  }

  return new Blob([icoData], { type: 'image/x-icon' })
}

// 转换图片
const convertImage = async () => {
  if (!selectedFile.value) {
    ElMessage.error('请先选择图片文件')
    return
  }

  if (selectedSizes.value.length === 0) {
    ElMessage.error('请至少选择一个 ICO 尺寸')
    return
  }

  converting.value = true

  try {
    const img = new Image()

    await new Promise((resolve, reject) => {
      img.onload = resolve
      img.onerror = () => reject(new Error('图片加载失败'))
      img.src = previewUrl.value
    })

    // 为每个选定的尺寸创建 Canvas
    const canvases: HTMLCanvasElement[] = []
    for (const size of selectedSizes.value.sort((a, b) => a - b)) {
      const canvas = await createCanvasForSize(img, size)
      canvases.push(canvas)

      // 生成预览并缓存
      const previewDataUrl = canvas.toDataURL('image/png')
      previewCache.value.set(size, previewDataUrl)
    }

    // 创建 ICO 文件
    const icoBlob = await createIcoFile(canvases)
    const dataUrl = URL.createObjectURL(icoBlob)

    convertedImage.value = {
      dataUrl,
      fileName: outputFileNameComputed.value,
      size: icoBlob.size,
      blob: icoBlob
    }

    ElMessage.success('转换成功！')
  } catch (error) {
    console.error('转换失败:', error)
    ElMessage.error('转换失败，请重试')
  } finally {
    converting.value = false
  }
}

// 获取指定尺寸的预览
const getPreviewForSize = (size: number): string => {
  return previewCache.value.get(size) || ''
}

// 下载图片
const downloadImage = () => {
  if (!convertedImage.value) {
    ElMessage.error('没有可下载的图片')
    return
  }

  const link = document.createElement('a')
  link.href = convertedImage.value.dataUrl
  link.download = convertedImage.value.fileName
  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link)

  ElMessage.success('下载已开始')
}

// 格式化文件大小
const formatFileSize = (bytes: number): string => {
  if (bytes === 0) return '0 Bytes'

  const k = 1024
  const sizes = ['Bytes', 'KB', 'MB', 'GB']
  const i = Math.floor(Math.log(bytes) / Math.log(k))

  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
</script>

<style scoped>
.png-to-ico-converter {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.upload-area {
  border: 2px dashed #dcdfe6;
  border-radius: 8px;
  padding: 40px;
  text-align: center;
  cursor: pointer;
  transition: border-color 0.3s;
  margin-bottom: 20px;
}

.upload-area:hover {
  border-color: #409eff;
}

.upload-placeholder {
  color: #909399;
}

.upload-placeholder i {
  font-size: 48px;
  margin-bottom: 16px;
  display: block;
}

.upload-placeholder .tip {
  font-size: 12px;
  color: #c0c4cc;
  margin-top: 8px;
}

.file-info {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.preview-image {
  max-width: 200px;
  max-height: 200px;
  border-radius: 4px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}

.convert-options {
  background: #f5f7fa;
  padding: 20px;
  border-radius: 8px;
  margin-bottom: 20px;
}

.convert-options h3 {
  margin-top: 0;
  margin-bottom: 20px;
  color: #303133;
}

.option-item {
  display: flex;
  align-items: center;
  margin-bottom: 16px;
  gap: 12px;
}

.option-item label {
  min-width: 100px;
  font-weight: 500;
}

.option-item .el-select {
  flex: 1;
}

.option-item .tip {
  font-size: 12px;
  color: #909399;
  margin-left: 12px;
}

.convert-section {
  text-align: center;
  margin-bottom: 30px;
}

.result-section {
  border-top: 1px solid #ebeef5;
  padding-top: 20px;
}

.result-section h3 {
  color: #303133;
  margin-bottom: 20px;
}

.result-preview {
  display: flex;
  gap: 20px;
  align-items: flex-start;
  margin-bottom: 20px;
  padding: 20px;
  background: #f5f7fa;
  border-radius: 8px;
}

.ico-previews {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  min-width: 200px;
}

.ico-preview-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}

.ico-preview {
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  background: white;
  display: block;
}

.ico-preview-item span {
  font-size: 12px;
  color: #606266;
}

.result-info {
  flex: 1;
}

.result-info p {
  margin: 8px 0;
  color: #606266;
}

.download-section {
  text-align: center;
}

@media (max-width: 768px) {
  .png-to-ico-converter {
    padding: 10px;
  }

  .upload-area {
    padding: 20px;
  }

  .result-preview {
    flex-direction: column;
    text-align: center;
  }

  .option-item {
    flex-direction: column;
    align-items: stretch;
  }

  .option-item label {
    min-width: auto;
    margin-bottom: 8px;
  }

  .ico-previews {
    justify-content: center;
    min-width: auto;
  }
}
</style>
